package com.sap.apitest2.common;

import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Ignore;
import org.junit.Test;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;


public class DefaultTestCase {

	public static String TESTCASE_PROP = "com.sap.epm.bpc.apitest.common.testcase";
	public static ResourceBundle bundle;
	
	
	static{
		bundle = ResourceBundle.getBundle(TESTCASE_PROP); 
	}

	private List<APIDefinition> APIs;
	
	private List<Integer> ignoreAPIs;
	
	private List<APIDefinition> beforeAPIs;
	
	private List<APIDefinition> afterAPIs;
	
	private Map<Integer, String[]> APIParams;
	
	private Map<Integer, String> APIReqs;
	
	private Map<Integer, String> APIDescs;
	
	private Map<Integer, String[]> beforeAPIParams;
	
	private Map<Integer, String> beforeAPIReqs;
	
	private Map<Integer, String> beforeAPIDescs;
	
	private Map<Integer, String[]> afterAPIParams;
	
	private Map<Integer, String> afterAPIReqs;
	
	private Map<Integer, String> afterAPIDescs;
	
	private String name;

	private String description;
	private String ts;
	private List<Integer> bypassFormatChecks;
	
	public DefaultTestCase() {
		this(null, null);
	}
	
	public DefaultTestCase(String name) {
		this(name, null);
	}
	
	public void setName(String name) {
		this.name = name;
	}

	
	public DefaultTestCase(String name, String description) {
		this.name = name;
		this.description = description;
		this.APIs = new ArrayList<APIDefinition>();
		this.APIParams = new HashMap<Integer, String[]>();
		this.APIReqs = new HashMap<Integer, String>();
		this.APIDescs = new HashMap<Integer, String>();
		
		this.beforeAPIs = new ArrayList<APIDefinition>();
		this.beforeAPIParams = new HashMap<Integer, String[]>();
		this.beforeAPIReqs = new HashMap<Integer, String>();
		this.beforeAPIDescs = new HashMap<Integer, String>();
		
		this.afterAPIs = new ArrayList<APIDefinition>();
		this.afterAPIParams = new HashMap<Integer, String[]>();
		this.afterAPIReqs = new HashMap<Integer, String>();
		this.afterAPIDescs = new HashMap<Integer, String>();
		
		ignoreAPIs = new ArrayList<Integer>();
		this.bypassFormatChecks = new ArrayList<Integer>();
	}
	public void add(APIDefinition e, boolean ignore) {
		this.add(e, null, null, ignore, false);
	}
	
	public void add(APIDefinition e, String[] param, boolean ignore) {
		this.add(e, param, null, ignore, false);
	}
	
	public void add(APIDefinition e, String[] param, boolean ignore, boolean bypassFormatCheck) {
		this.add(e, param, null, ignore, bypassFormatCheck);
	}
	
	public void add(APIDefinition e, String[] param, String request, boolean ignore) {
		this.add(e, param, request, request, ignore, false);
	}
	
	public void add(APIDefinition e, String[] param, String request, boolean ignore, boolean bypassFormatCheck) {
		this.add(e, param, request, null, ignore, bypassFormatCheck);
		
	}
	
	public void add(APIDefinition e, String[] param, String request, String desc, boolean ignore, boolean bypassFormatCheck) {
		this.add(e, param, request, desc);
		if(ignore)
			this.ignoreAPIs.add(APIs.size()-1);
		if (bypassFormatCheck) {
			this.bypassFormatChecks.add(APIs.size() - 1);
		}
	}

	public void add(APIDefinition e) {
		this.add(e, null, null);
	}
	
	public void add(APIDefinition e, String[] param) {
		this.add(e, param, null);
	}
	
	public void add(APIDefinition e, String[] param, String request) {
		this.add(e, param, request, null);
		
	}
	
	public void add(APIDefinition e, String[] param, String request, String desc) {
		if(request==null) {
			int index = APIs.size();
			final String simpleName = this.getName()==null?this.getClass().getSimpleName():this.getName();
			String path = bundle.getString("req_folder");
			File folder = new File(path+"/"+this.getClass().getPackage().getName().replace('.', '/'));
			final String fn = simpleName+"."+index+".req";
			File f = new File(folder, fn);
			if(f.exists()) {
				FileReader fr = null;
				try{
					StringBuffer sb = new StringBuffer();
					char[] c = new char[128];
					fr = new FileReader(f);
					int i=0;
					while((i=fr.read(c))>0) {
						sb.append(c, 0, i);
					}
					
					request = sb.toString();	
				} catch (Exception ex) {
					ex.printStackTrace();
				} finally {
					try{
						fr.close();
					}catch (Exception e1) {}
				}
			}
		}
		APIParams.put(new Integer(APIs.size()), param);
		APIReqs.put(new Integer(APIs.size()), request);
		APIDescs.put(new Integer(APIs.size()), desc);
		APIs.add(e);
		e.addTaestCase(this);
	}
	
	
	public void addBefore(APIDefinition e) {
		this.addBefore(e, null, null);
	}
	
	public void addBefore(APIDefinition e, String[] param) {
		this.addBefore(e, param, null);
	}
	
	public void addBefore(APIDefinition e, String[] param, String request) {
		this.addBefore(e, param, request, null);
		
	}
	
	public void addBefore(APIDefinition e, String[] param, String request, String desc) {
		if(request==null) {
			int index = beforeAPIs.size();
			final String simpleName = this.getName()==null?this.getClass().getSimpleName():this.getName();
			String path = bundle.getString("req_folder");
			File folder = new File(path+"/"+this.getClass().getPackage().getName().replace('.', '/'));
			final String fn = simpleName+"."+index+".before";
			File f = new File(folder, fn);
			if(f.exists()) {
				FileReader fr = null;
				try{
					StringBuffer sb = new StringBuffer();
					char[] c = new char[128];
					fr = new FileReader(f);
					int i=0;
					while((i=fr.read(c))>0) {
						sb.append(c, 0, i);
					}
					
					request = sb.toString();	
				} catch (Exception ex) {
					ex.printStackTrace();
				} finally {
					try{
						fr.close();
					}catch (Exception e1) {}
				}
			}
		}
		beforeAPIParams.put(new Integer(beforeAPIs.size()), param);
		beforeAPIReqs.put(new Integer(beforeAPIs.size()), request);
		beforeAPIDescs.put(new Integer(beforeAPIs.size()), desc);
		beforeAPIs.add(e);
		e.addTaestCase(this);
	}
	
	public void addAfter(APIDefinition e) {
		this.addAfter(e, null, null);
	}
	
	public void addAfter(APIDefinition e, String[] param) {
		this.addAfter(e, param, null);
	}
	
	public void addAfter(APIDefinition e, String[] param, String request) {
		this.addAfter(e, param, request, null);
		
	}
	
	public void addAfter(APIDefinition e, String[] param, String request, String desc) {
		if(request==null) {
			int index = beforeAPIs.size();
			final String simpleName = this.getName()==null?this.getClass().getSimpleName():this.getName();
			String path = bundle.getString("req_folder");
			File folder = new File(path+"/"+this.getClass().getPackage().getName().replace('.', '/'));
			final String fn = simpleName+"."+index+".after";
			File f = new File(folder, fn);
			if(f.exists()) {
				FileReader fr = null;
				try{
					StringBuffer sb = new StringBuffer();
					char[] c = new char[128];
					fr = new FileReader(f);
					int i=0;
					while((i=fr.read(c))>0) {
						sb.append(c, 0, i);
					}
					
					request = sb.toString();	
				} catch (Exception ex) {
					ex.printStackTrace();
				} finally {
					try{
						fr.close();
					}catch (Exception e1) {}
				}
			}
		}
		afterAPIParams.put(new Integer(afterAPIs.size()), param);
		afterAPIReqs.put(new Integer(afterAPIs.size()), request);
		afterAPIDescs.put(new Integer(afterAPIs.size()), desc);
		afterAPIs.add(e);
		e.addTaestCase(this);
	}
	
	public int size() {
		return APIs.size();
	}

	public String getName() {
		return name;
	}

	public String getDescription() {
		return description;
	}
	
	public void setDescription(String description) {
		this.description = description;
	}
	
	
	public synchronized boolean executeAPIs() {
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyMMdd HHmmss");
		ts = simpleDateFormat.format(new Date());
		for(int i = 0; i < APIs.size(); i++) {
			try{
				APIDefinition api = APIs.get(i);
				APIExecutor apiExecutor = new APIExecutor(api, APIParams.get(i), APIReqs.get(i), this.getClass().getPackage().getName());
				apiExecutor.execute();
				System.out.println(api.getURIPattern());
				if(!ignoreAPIs.contains(i)) {
					boolean check = check(apiExecutor, i);
					if(!check) return false;	
				}				
			} catch(IOException e) {
				return false;
			}
			
		}
		return true;
	}
	
	public synchronized boolean executeBeforeAPIs() {

		for(int i = 0; i < beforeAPIs.size(); i++) {
			try{
				APIDefinition api = beforeAPIs.get(i);
				APIExecutor apiExecutor = new APIExecutor(api, beforeAPIParams.get(i), beforeAPIReqs.get(i), this.getClass().getPackage().getName());
				apiExecutor.execute();
				boolean check = check(apiExecutor, i);
				if(!check) return false;
			} catch(IOException e) {
				return false;
			}
			
		}
		return true;
	}
	
	public synchronized boolean executeAfterAPIs() {

		for(int i = 0; i < afterAPIs.size(); i++) {
			try{
				APIDefinition api = afterAPIs.get(i);
				APIExecutor apiExecutor = new APIExecutor(api, afterAPIParams.get(i), afterAPIReqs.get(i), this.getClass().getPackage().getName());
				apiExecutor.execute();
				boolean check = check(apiExecutor, i);
				if(!check) return false;
			} catch(IOException e) {
				return false;
			}
			
		}
		return true;
	}
	
	protected boolean compare(String actual, String expect) throws Exception{
		actual = actual.replaceAll("<log_id>.*</log_id>", "");
		expect = expect.replaceAll("<log_id>.*</log_id>", "");
		actual = actual.replaceAll("\"version\": \".*\"", "\"version\": \"\"");
		expect = expect.replaceAll("\"version\": \".*\"", "\"version\": \"\"");

		return actual.equals(expect);
	}
	
	protected boolean checkFormatError(String actual) {
		return actual.indexOf("<description>Invalid XML format</description>")==-1 && actual.indexOf("<description>Invalid JSON format</description>")==-1;
	}
	
	public boolean check(APIExecutor apiExecutor, final int index) {
		final String simpleName = this.getName()==null?this.getClass().getSimpleName():this.getName();
		boolean result = false;
		boolean doCheck = true;
		String message = new String();
		String path = bundle.getString("resp_folder");
		File folder = new File(path+"/"+this.getClass().getPackage().getName().replace('.', '/'));
		if(!folder.exists())
			folder.mkdirs();
		final String fn = simpleName+"."+index+"."+apiExecutor.getStatusCode()+".rep";
		File f = new File(folder, fn);
		String actualRep = null;
		try {

			actualRep = format(apiExecutor.getResponseAsString());
		} catch (UnsupportedEncodingException e2) {
			message = e2.getLocalizedMessage();
			result = false;
			doCheck = false;
		} catch (JSONException e2) {
			message = e2.getLocalizedMessage();
			result = false;
			doCheck = false;
		}
		boolean bypassFormatCheck = isBypassFormatCheck(index);
		if(doCheck && f.exists()) {
			FileReader fr = null;
			try{
				StringBuffer sb = new StringBuffer();
				char[] c = new char[128];
				fr = new FileReader(f);
				int i=0;
				while((i=fr.read(c))>0) {
					sb.append(c, 0, i);
				}
				String expectRep = format(sb.toString());
				
				if(!bypassFormatCheck && !checkFormatError(apiExecutor.getResponseAsString())) {
					message = "Invalid Format!";
					result = false;
				} else if(compare(actualRep, expectRep)) {
					result = true;
					assertTrue(result);
					return result;
				} else {
					message = "actual:\n"+actualRep+"\n"
					+"expected:\n"+expectRep;
					result = false;
				}
				folder = new File(folder+"/actural");
				if(!folder.exists())
					folder.mkdirs();
				f = new File(folder, fn+".actural-"+ts);
					
			} catch (Exception e) {
				e.printStackTrace();
				message = e.getLocalizedMessage();
				result = false;
			} finally {
				try{
					fr.close();
				}catch (Exception e1) {}
			}
		} else {
			
			try {
				if(!bypassFormatCheck && !checkFormatError(apiExecutor.getResponseAsString())) {
					message = "Invalid Format!";
					result = false;
				} else {
					FilenameFilter filer = new FilenameFilter() {
						@Override
						public boolean accept(File dir, String name) {
							if(name.startsWith(simpleName+"."+index+".") && name.endsWith(".rep"))
								return true;
							else
								return false;
						}
					};
					File[] listFiles = folder.listFiles(filer);
					if(listFiles.length!=0) {
						folder = new File(folder+"/actural");
						if(!folder.exists())
							folder.mkdirs();
						f = new File(folder, fn+".actural-"+ts);
						message = "actual:\n"+fn+"\nexpected:\n"+listFiles[0].getName()+"\nactual response:\n"+actualRep+"\n";
						result = false;
					} else {
						result = true;
					}
				}
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
				message = e.getLocalizedMessage();
				result = false;
			} 
			
			
		}
		
		
		FileWriter fw = null;
		try{
			fw = new FileWriter(f);
			fw.write(actualRep);
		} catch (Exception e) {
			e.printStackTrace();
			assertTrue(false);
			return false;
		} finally {
			try{
				fw.close();
			}catch (Exception e1) {}
		}
		assertTrue(message, result);
		
		return result;
	}
	
	
	private boolean isBypassFormatCheck(int index) {
		return this.bypassFormatChecks.contains(index);
	}

	private static String format(String txt) throws JSONException {
		
		if(txt.startsWith("<HTML")) {
			return txt;
			
		} else if(txt.startsWith("<")) {
			return formatXML(txt);
			
		} else if(txt.startsWith("[")) {
			JSONArray jsonArray = new JSONArray(txt);
			return  (jsonArray.toString(1));
			
		} else if(txt.startsWith("{")) {
			JSONObject jsonObj = new JSONObject(txt);
			return (jsonObj.toString(1));
		} else {
			return txt;
		}
	}
	
	private static String formatXML(String xml) {

        try {
    		StringReader sr = new StringReader(xml);
    		InputSource is = new InputSource(sr);
    		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    		DocumentBuilder builder=factory.newDocumentBuilder();
    		Document doc = builder.parse(is); 
    		StringWriter sw = new StringWriter();
    		 TransformerFactory tfactory = TransformerFactory.newInstance();
    	        Transformer serializer;
            serializer = tfactory.newTransformer();
            //Setup indenting to "pretty print"
            serializer.setOutputProperty(OutputKeys.INDENT, "yes");
            serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
            
            serializer.transform(new DOMSource(doc), new StreamResult(sw));
            return sw.getBuffer().toString();
        } catch (TransformerException e) {
            e.printStackTrace();
        } catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		return xml;
	       
	}
	@Ignore
	@Test
	public void none() {
		
	}
}
